package com.share.common.rabbit.config;

import com.alibaba.fastjson2.JSON;
import com.share.common.rabbit.entity.GuiguCorrelationData;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * RabbitMQ初始化配置监听器
 * 在应用启动完成后初始化RabbitMQ的回调函数，处理消息发送确认和返回机制
 * 实现消息发送失败时的重试逻辑，确保消息可靠性
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RabbitInitConfigApplicationListener implements ApplicationListener<ApplicationReadyEvent> {

    /**
     * RabbitTemplate实例，用于发送消息到RabbitMQ
     */
    private final RabbitTemplate rabbitTemplate;

    /**
     * RedisTemplate实例，用于存储消息相关数据和重试计数
     */
    private final RedisTemplate redisTemplate;


    /**
     * 应用启动完成事件回调
     * 在Spring容器初始化完成后执行RabbitMQ回调设置
     *
     * @param event 应用就绪事件对象
     */
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        // 设置RabbitMQ的确认和返回回调
        this.setupCallbacks();
    }

    /**
     * 设置RabbitMQ的回调函数
     * 包括消息发送到交换机的确认回调和消息无法路由到队列的返回回调
     */
    private void setupCallbacks() {

        /**
         * 消息发送到交换机(Exchange)的确认回调
         * 无论消息是否成功到达交换机，都会触发此回调
         *
         * @param correlationData 消息相关数据（包含消息ID等元数据）
         * @param ack 消息是否成功到达交换机（true成功，false失败）
         * @param reason 消息发送失败的原因（ack为false时有效）
         */
        this.rabbitTemplate.setConfirmCallback((correlationData, ack, reason) -> {
            if (ack) {
                // 消息成功到达交换机
                log.info("消息发送到Exchange成功：{}", correlationData);
            } else {
                // 消息发送到交换机失败
                log.error("消息发送到Exchange失败：{}", reason);

                // 执行消息重发逻辑
                this.retrySendMsg(correlationData);
            }
        });

        /**
         * 消息返回回调（Return Callback）
         * 当消息成功到达交换机，但无法路由到任何队列时触发
         */
        this.rabbitTemplate.setReturnsCallback(returned -> {
            log.error("消息路由到队列失败: " +
                    "\n消息内容: " + returned.getMessage() +
                    "\n错误代码: " + returned.getReplyCode() +
                    "\n错误信息: " + returned.getReplyText() +
                    "\n交换机/路由键: " + returned.getExchange() + "/" + returned.getRoutingKey());

            // 从消息头中获取关联数据的Redis键
            String redisKey = returned.getMessage().getMessageProperties().getHeader("spring_returned_message_correlation");
            // 从Redis中获取完整的关联数据
            String correlationDataStr = (String) redisTemplate.opsForValue().get(redisKey);
            GuiguCorrelationData guiguCorrelationData = JSON.parseObject(correlationDataStr, GuiguCorrelationData.class);

            // 如果是延迟消息，不进行重发（可根据业务需求调整）
            if (guiguCorrelationData.isDelay()) {
                return;
            }

            // 调用消息重发方法
            this.retrySendMsg(guiguCorrelationData);
        });
    }

    /**
     * 消息重新发送逻辑
     * 实现消息发送失败后的重试机制，限制最大重试次数
     *
     * @param correlationData 消息关联数据对象，包含消息内容、交换机、路由键等信息
     */
    private void retrySendMsg(CorrelationData correlationData) {
        // 转换为自定义的关联数据对象
        GuiguCorrelationData gmallCorrelationData = (GuiguCorrelationData) correlationData;

        // 获取当前重试次数
        int retryCount = gmallCorrelationData.getRetryCount();

        // 检查是否超过最大重试次数（这里设置为3次）
        if (retryCount >= 3) {
            // 超过最大重试次数，记录错误日志，可考虑持久化失败消息供人工处理
            log.error("消息超过最大重试次数(3次)，将失败的消息记录到数据库待人工处理：{}", gmallCorrelationData.getId());
            // 实际应用中可添加发送邮件/短信通知管理员的逻辑
            return;
        }

        // 重试次数加1
        retryCount += 1;
        gmallCorrelationData.setRetryCount(retryCount);

        // 更新Redis中存储的关联数据，设置过期时间为10分钟
        redisTemplate.opsForValue().set(
                gmallCorrelationData.getId(),
                JSON.toJSONString(gmallCorrelationData),
                10,
                TimeUnit.MINUTES
        );

        log.info("消息开始第{}次重发，消息ID：{}", retryCount, gmallCorrelationData.getId());

        // 根据消息类型执行不同的重发逻辑
        if (gmallCorrelationData.isDelay()) {
            // 延迟消息重发，需要重新设置延迟时间
            rabbitTemplate.convertAndSend(
                    gmallCorrelationData.getExchange(),
                    gmallCorrelationData.getRoutingKey(),
                    gmallCorrelationData.getMessage(),
                    message -> {
                        // 设置延迟时间（单位：毫秒）
                        message.getMessageProperties().setDelay(gmallCorrelationData.getDelayTime() * 1000);
                        return message;
                    },
                    gmallCorrelationData
            );
        } else {
            // 普通消息重发
            rabbitTemplate.convertAndSend(
                    gmallCorrelationData.getExchange(),
                    gmallCorrelationData.getRoutingKey(),
                    gmallCorrelationData.getMessage(),
                    gmallCorrelationData
            );
        }
    }
}